/*
 * Copyright (c) 2007 centrevillage <centrevillage@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef  __FUNCTION_UTIL_HPP__
#define  __FUNCTION_UTIL_HPP__

// boost
#include <boost/format.hpp>
#include <boost/rational.hpp>

// ravi
#include "error.hpp"
#include "types.hpp"
#include "util.hpp"

namespace ravi {

inline
void args_guard_eq(const std::vector<val_t>& argv, size_t nargs) { 
    using namespace boost; using io::str;
    if (argv.size() != nargs) {
        throw compile_error(str(format("Number of arguments must be %d! (%d)")%nargs%argv.size()));
    }
}

inline
void args_guard_ls(const std::vector<val_t>& argv, size_t nargs) { 
    using namespace boost; using io::str;
    if (!(argv.size() < nargs)) {
        throw compile_error(str(format("Number of arguments must be less than %d! (%d)")%nargs%argv.size()));
    }
}

inline
void args_guard_gl(const std::vector<val_t>& argv, size_t nargs) { 
    using namespace boost; using io::str;
    if (!(argv.size() > nargs)) {
        throw compile_error(str(format("Number of arguments must be greater than %d! (%d)")%nargs%argv.size()));
    }
}

inline
void args_guard_ls_eq(const std::vector<val_t>& argv, size_t nargs) { 
    using namespace boost; using io::str;
    if (!(argv.size() <= nargs)) {
        throw compile_error(str(format("Number of arguments must be equal or less than %d! (%d)")%nargs%argv.size()));
    }
}

inline
void args_guard_gl_eq(const std::vector<val_t>& argv, size_t nargs) { 
    using namespace boost; using io::str;
    if (!(argv.size() >= nargs)) {
        throw compile_error(str(format("Number of arguments must be equal or greater than %d! (%d)")%nargs%argv.size()));
    }
}

inline
void int_guard_u(size_t argn, int val) {
    using namespace boost; using io::str;
    if (val < 0) {
        throw compile_error(str(format("Value of argument %d must be positive.")%argn));
    }
}

inline
void rtn_guard_u(size_t argn, const boost::rational<int>& val) {
    using namespace boost; using io::str;
    if (val < 0) {
        throw compile_error(str(format("Value of argument %d must be positive.")%argn));
    }
}


inline
void argv_guard_type(const std::vector<val_t>& argv, size_t argn, int id) {
    using namespace boost; using io::str;
    if (argv[argn].type != id) {
        throw compile_error(str(format("Type of argument %d must be %s.")%argn%type2name(id)));
    }
}

inline
void argv_guard_type2(const std::vector<val_t>& argv, size_t argn, int id1, int id2) {
    using namespace boost; using io::str;
    if (!(argv[argn].type == id1 || argv[argn].type == id2)) {
        throw compile_error(str(format("Type of argument %d must be %s or %s.")%argn%type2name(id1)%type2name(id2)));
    }
}


inline
bool is_valid_list_format(const val_t& argv, const char* str, bool permitEmpty = false) {
    if (argv.type != lst_id) return false;
    lst_t* arglv = get_lst(argv);
    if (permitEmpty && arglv->empty()) return true;
    int prev_type = nul_id;
    size_t i = 0;
    for (i = 0; *str != 0; ++str, ++i) {
        switch (*str) {
            case '@': // null
                if (i >= arglv->size() || (*arglv)[i].type != nul_id) return false;
                break;
            case 'i': // int
                if (i >= arglv->size() || (*arglv)[i].type != int_id) return false;
                break;
            case 's': // str
                if (i >= arglv->size() || (*arglv)[i].type != str_id) return false;
                break;
            case 'r': // rational
                if (i >= arglv->size() || (*arglv)[i].type != rtn_id) return false;
                break;
            case 'l': // list
                if (i >= arglv->size() || (*arglv)[i].type != lst_id) return false;
                break;
            case 'm': // map
                if (i >= arglv->size() || (*arglv)[i].type != map_id) return false;
                break;
            case 'f': // function
                if (i >= arglv->size() || (*arglv)[i].type != fnc_id) return false;
                break;
            case 'R': // range
                if (i >= arglv->size() || (*arglv)[i].type != rng_id) return false;
                break;
            case 'n': // native function
                if (i >= arglv->size() || (*arglv)[i].type != nfn_id) return false;
                break;
            case 'F': // function
                if (i >= arglv->size() || ((*arglv)[i].type != nfn_id && (*arglv)[i].type != fnc_id)) return false;
                break;
            case '_': // val ( any )
                if (i >= arglv->size()) return false;
                break;
            case '+': // repeat prev type
                for (; i < arglv->size(); ++i) {
                    if ((*arglv)[i].type != prev_type) return false;
                }
                return true;
            default:
                return false;
        }
        prev_type = (*arglv)[i].type;
    }
    if (i != arglv->size()) {
        return false;
    }
    return true;
}

inline
void check_list_format(const val_t& v, const char* str) {
    if (!is_valid_list_format(v, str)) {
        throw compile_error("Invalid list-type argument.");
    }
}

}		/* -----  end of namespace  ravi  ----- */

#endif   /* ----- #ifndef __FUNCTION_UTIL_HPP__  ----- */
